"use strict";
let _evt = null;
let _pslHost = psl.get(window.location.hostname);
let _host = (_pslHost === null ? window.location.host : _pslHost);
let _data = [];
let _src_set = new Set();

function getJsonData(force) {
	return new Promise(async (resolve, reject) => {
		if (force) {
			_data = [];
			_src_set = new Set();
			let rj = await getRootJson();
			for (let jsonObj of rj.data) {
				if (!jsonObj) {
					continue;
				}
				// host可以为*,代表所有站点;
				if (jsonObj.host==="*" || jsonObj.host===_host) {
					// 修改符合条件的元素的可见属性;
					_data.push(jsonObj);

					if (_enable_src_cache) {
						let match = jsonObj.elem.match(/\[src='(.*?[^\\])'\]/i);
						if (match && match.length>1) {
							_src_set.add(match[1]);
						}
					}
				}
			}
		}
		resolve(_data);
	});
}

function getPathStr(elem, useFullPath) {
	let parent = elem;
	let pathStr = elem.tagName;
	if (!useFullPath) {
		return pathStr;
	}
	while ((parent.parentNode || parent.host) && parent!==document.body) {
		parent = parent.parentNode || parent.host;
		let attrStr = "";
		if (parent.id) {
			attrStr = "#" + parent.id.trim();
		} else if (parent.className) {
			attrStr = "." + parent.className.trim().replace(/\s+/g, ".");
		}
		if (parent.tagName) {
			pathStr = parent.tagName + attrStr + " > " + pathStr;
		}
	}
	return pathStr;
}

function getContainsStr(elem) {
	if (elem.tagName === "IMG") {
		return "";
	}
	return ":contains('" + elem.textContent.trim().replace(/\n/g, "\\n").replace(/'/g, "\\'") + "')";
}

function getAttrStr(elem, useFullAttr) {
	let attrStr = "";
	if ($(elem).text().trim() != "" && !useFullAttr) {
		return attrStr;
	}
	attrStr = getAttrStrByRegExp(elem, (elem.tagName==="IMG" && !useFullAttr) ? "^src$" : "^[\\w\\-]+$");
	if (attrStr === "") {
		attrStr = getAttrStrByRegExp(elem, "^[\\w\\-]+$");
	}
	return attrStr;
}

function getAttrStrByRegExp(elem, strPattern) {
	let attrStr = "";
	let objRegExp = new RegExp(strPattern, "i");
	let attrs = elem.attributes;
	for (let i=0; i<attrs.length; i++) {
		if (objRegExp.test(attrs[i].name)) {
			let attrValue = attrs[i].value.replace(/\n/g, "\\n").replace(/'/g, "\\'");
			attrStr += "[" + attrs[i].name + (attrValue === "" ? "" : "='" + attrValue + "'") + "]";
		}
	}
	return attrStr;
}

function hideItem(data, elem) {
	if (data.host==="*" || _host===data.host) {
		let items = $(data.elem);
		if (elem) {
			items = items.add(elem);
		}
		console.log("hide-item: " + items.length + " " + data.elem);
		items.each(function(){setHiddenStyle($(this), data);});
	}
}

function setHiddenStyle(elem, data) {
	if (hasOption(data, "disabled")) {
		return;
	}
	if (data != null) {
		elem.data("recyclebin-guid", data.guid);
		elem.data("data", data);
	}
	elem.data("nostyle", typeof elem.attr("style")=="undefined");
	if (hasOption(elem.data("data"), "display:none")) {
		elem.css("display", "none");
	}
	elem.css("visibility", "hidden");
}

function clearHiddenStyle(elem, forever) {
	if (hasOption(elem.data("data"), "prevent_delete")) {
		return;
	}
	elem.css("visibility", "");
	if (hasOption(elem.data("data"), "display:none")) {
		elem.css("display", "");
	}
	if (elem.data("nostyle") && elem.attr("style")=="") {
		elem.removeAttr("style");
	}
	if (forever) {
		elem.removeData("recyclebin-guid");
		elem.removeData("data");
		elem.removeData("nostyle");
	}
}

function allElems(elem) {
	let $elems = $(elem).find("*").addBack();
	$elems.each(function(index, element) {
		let root = element.shadowRoot;
		if (root) {
			$elems = $elems.add(allElems(root));
		}
	});
	return $elems;
}

function throwIntoTheRecycleBin(elem, x, y, isGlobal, doReport) {
	let blnDoPush = true;
	while (elem.shadowRoot) {
		let targetElement = elem.shadowRoot.elementFromPoint(x, y);
		if (targetElement !== elem) {
			elem = targetElement;
		} else {
			break;
		}
	}
	// 不处理视频元素;
	let ve = $(elem.tagName==="VIDEO"?elem:"").add($(elem).parents("VIDEO")).add($(elem).find("VIDEO"));
	if (ve.length > 0) {
		if (ve.width() > document.documentElement.clientWidth/3 && ve.height() > document.documentElement.clientHeight/3) {
			console.log("回收站: 忽略大尺寸视频.");
			blnDoPush = false;
		}
	}
	while (elem.style && elem.style.visibility==="hidden") {
		if (elem.parentNode) {
			elem = elem.parentNode;
		} else if (elem.host) {
			elem = elem.host;
		} else {
			break;
		}
	}
	if (elem.tagName === "HTML") {
		console.log("回收站: 不能处理网页的顶层HTML元素.");
		return;
	}
	// 将该元素所包含的文本和所有属性转换为JQuery查询语句;
	// data为该元素的JQuery查询语句;
	let data = getPathStr(elem, false) + getContainsStr(elem) + getAttrStr(elem, false);
	let selector = getPathStr(elem, true) + getContainsStr(elem) + getAttrStr(elem, true);
	// 生成不良元素的信息对象;
	let jsonObj = {
		elem: data,
		selector: selector,
		html: elem.outerHTML,
		time: new Date().toString(),
		guid: newGuid(),
		host: (isGlobal ? "*" : _host),
		title: document.title,
		frame_url: window.location.href
	};
	// 将不良信息推入回收站;
	chrome.runtime.sendMessage({name: "push-item", data: jsonObj, doPush: blnDoPush}, async function(result) {
		blnDoPush = result.doPush;

		if (blnDoPush) {
			console.dir(result.data);
		} else if (result.inWhiteList) {
			console.log("回收站: 网址在白名单中!");
		}

		if (doReport) {
			let items = $(result.data.elem).add(elem);
			allElems("body").each(function() {
				if ($(this).data("recyclebin-guid")) {
					clearHiddenStyle($(this), false);
				}
			});
			items.css("outline", "solid 3px rgba(255,0,0,0.5)").css("outlineOffset", "-3px");
			let p1 = new Promise((resolve, reject) => {
				window.setTimeout(function() {
					chrome.runtime.sendMessage({name: "capture-page", data: result.data}, async function() {
						let p2 = new Promise((resolve, reject) => {
							window.setTimeout(function() {
								// 隐藏body中的所有对应有recyclebin-guid的元素;
								allElems("body").each(function() {
									if ($(this).data("recyclebin-guid")) {
										setHiddenStyle($(this), null);
									}
								});
								items.css("outline", "").css("outlineOffset", "");
								if (blnDoPush) {
									hideItem(result.data, elem);
								}
								resolve();
							}, 1000);
						});
						await Promise.all([p2]);
						resolve();
					});
				}, 1000);
			});
			await Promise.all([p1]);
		} else if (blnDoPush) {
			hideItem(result.data, elem);
		}
	});
}

function main() {
	updateOptions();

	chrome.runtime.onMessage.addListener(async function(message, sender, sendResponse) {
		//console.log("onMessage: " + message.name);

		switch (message.name) {
		case "hide-item":
			hideItem(message.data, null);
			break;
		case "restore-item":
			if (message.data.host==="*" || message.data.host===_host) {
				allElems("body").each(function() {
					if ($(this).data("recyclebin-guid") === message.data.guid) {
						clearHiddenStyle($(this), true);
					}
				});
			}
			break;
		case "init-page":
			console.log(message.data);
			break;
		case "update-data":
			getJsonData(true);
			break;
		case "clear-data":
			allElems("body").each(function() {
				if ($(this).data("recyclebin-guid")) {
					clearHiddenStyle($(this), true);
				}
			});
			break;
		case "update-options":
			const old_enable_src_cache = _enable_src_cache;
			await updateOptions();
			if (_enable_src_cache !== old_enable_src_cache) {
        		getJsonData(true);
			}
			break;
		case "throw-into-the-recyclebin":
			throwIntoTheRecycleBin(_evt.srcElement, _evt.clientX, _evt.clientY, false, false);
			break;
		case "throw-into-the-global-recyclebin":
			throwIntoTheRecycleBin(_evt.srcElement, _evt.clientX, _evt.clientY, true, false);
			break;
		case "throw-into-the-recyclebin-and-report":
			throwIntoTheRecycleBin(_evt.srcElement, _evt.clientX, _evt.clientY, false, true);
			break;
		case "pop-item":
		case "clear-all-data":
			chrome.runtime.sendMessage({name: message.name});
			break;
		case "toggle-selecting":
			_is_selecting = message.is_selecting;
			break;
		}
	});

	// 监听指定元素及所有后代元素的鼠标右键点击事件;
	window.addEventListener("contextmenu", function(evt) {
		if (_is_selecting) {
			console.log("right-click-toggle-selecting");
			chrome.runtime.sendMessage({name: "toggle-selecting"}).catch(error => {
				showError("right-click-toggle-selecting", error);
			});
			evt.stopPropagation();
			evt.preventDefault();
			return false;
		}
		_evt = evt;
		// 定位shadowRoot中的元素;
		// 如果同时按了ctrl按键则进行处理;
		if (evt.ctrlKey) {
			// 移入回收站时按住shift的话有可能选中网页内容,这里取消选择;
			if (evt.shiftKey) {
				window.getSelection().removeAllRanges();
			}
			// evt.srcElement为鼠标点击的元素;
			throwIntoTheRecycleBin(evt.srcElement, evt.clientX, evt.clientY, evt.altKey, evt.shiftKey);
			evt.stopPropagation();
			evt.preventDefault();
			return false;
		} else {
			return true;
		}
	}, true);

	window.addEventListener("click", function(evt) {
		_evt = evt;
		if (_is_selecting) {
			if (!_continue_selecting_mode) {
				console.log("left-click-toggle-selecting");
				chrome.runtime.sendMessage({name: "toggle-selecting"}).catch(error => {
					showError("left-click-toggle-selecting", error);
				});
			}
			throwIntoTheRecycleBin(evt.srcElement, evt.clientX, evt.clientY, false, false);
			evt.stopPropagation();
			evt.preventDefault();
			return false;
		} else {
			return true;
		}
	}, true);

	window.addEventListener("keydown", function(evt) {
		if (_is_selecting && evt.key === "Escape") {
			console.log("escape-keydown-toggle-selecting");
			chrome.runtime.sendMessage({name: "toggle-selecting"}).catch(error => {
				showError("escape-keydown-toggle-selecting", error);
			});
			evt.stopPropagation();
			evt.preventDefault();
			return false;
		} else {
			return true;
		}
	}, true);
}

$(document).ready(function() {

	// 修改body的所有元素的可见属性;
	async function updateAllContent() {
		let p1 = new Promise(async (resolve, reject) => {					
			console.time("回收站: updateAllContent");
			let arrdata = await getJsonData(true);
			for (let jsonObj of arrdata) {
				if (!jsonObj) {
					continue;
				}
				let p2 = new Promise((resolve, reject) => {
					// 修改符合条件的元素的可见属性;
					$(jsonObj.elem).each(function(){setHiddenStyle($(this), jsonObj);});
					resolve();
				});
				await Promise.all([p2]);
			}

			console.timeEnd("回收站: updateAllContent");
			resolve();
		});
		return await Promise.all([p1]);
	}

	// 修改符合条件的元素的可见属性;
	async function updateContent(elem, onlyone) {
		let p1 = new Promise((resolve, reject) => {

			// 根据性能模式决定是否使用 setTimeout 包装;
			const execWithDelay = (fn) => _performance_mode ? fn : (() => setTimeout(fn, 0));

			execWithDelay(async () => {
				if (!elem) {
					return;
				}
				if (typeof(elem) !== "object") {
					return;
				}
				let $elems;
				if (onlyone) {
					$elems = $(elem);
				} else {
					let maxLevel = _dynamic_filter_level;
					let parent = elem;
					let level = 0;
					for (let i=0; i<maxLevel && parent.parentNode && parent!==document.body; i++) {
						parent = parent.parentNode;
						level++;
					}
					// console.log(level);
					// console.log(getPathStr(parent, true));
					$elems = $(parent).find("*").addBack();
				}
				let arrdata = await getJsonData(false);
				let len = 0;
				for (let jsonObj of arrdata) {
					if (!jsonObj) {
						continue;
					}
					let p2 = new Promise((resolve, reject) => {
						const execWithDelay2 = (fn) => _performance_mode ? fn : (() => setTimeout(fn, 0));

						execWithDelay2(() => {
							len += $elems.filter(jsonObj.elem).each(function(){setHiddenStyle($(this), jsonObj);}).length;
							resolve();
						})();
					});
					await Promise.all([p2]);
				}
				if (onlyone && len===0 && $elems.data("recyclebin-guid")) {
					clearHiddenStyle($elems, true);
				}
				resolve();
			})();
		});
		return await Promise.all([p1]);
	}

	// 修改body及其中的所有元素;
	updateAllContent();

	// 监测DOM的变化;
	let mo = new MutationObserver(function(mutationList, observer) {
		for (let mutation of mutationList) {
			switch (mutation.type) {
			case "childList":
				for (let node of mutation.addedNodes) {
					switch (node.nodeName.toLowerCase())
					{
					case "#comment":
					case "#text":
					case "path":
					case "g":
					case "br":
					case "script":
						break;  // 忽略这些浪费时间且无用的元素;
					default:
						updateContent(node, false);
						break;
					}
				}
				for (let node of mutation.removedNodes) {
					updateContent(node.parentNode, false);
				}
				break;
			case "characterData":
				updateContent(mutation.target.parentNode || mutation.target.host, true);
				break;
			case "attributes":
				if (mutation.attributeName === "src") {
					let elem = $(mutation.target);
					if (!_enable_src_cache || _src_set.has(mutation.target.getAttribute("src"))) {
						updateContent(mutation.target, true);
					} else if (elem.data("recyclebin-guid")) {
						updateContent(mutation.target, true);
					}
				}
				break;
			}
		}
	});

	const config = {
		subtree: true, 
		childList: true, 
		characterData: true, 
		attributes: true, 
		attributeFilter: ["src"]
	};
	mo.observe(document.body, config);
});

main();
